﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Text.RegularExpressions;

namespace Sigil.Impl
{
	internal class BufferedILInstruction
	{
		public Boolean DefinesLabel { get; internal set; }

		public Sigil.Label MarksLabel { get; internal set; }

		public Boolean StartsExceptionBlock { get; internal set; }

		public Boolean EndsExceptionBlock { get; internal set; }

		public Boolean StartsCatchBlock { get; internal set; }

		public Boolean EndsCatchBlock { get; internal set; }

		public Boolean StartsFinallyBlock { get; internal set; }

		public Boolean EndsFinallyBlock { get; internal set; }

		public Boolean DeclaresLocal { get; internal set; }

		public OpCode? IsInstruction { get; internal set; }

		public Type MethodReturnType { get; internal set; }

		public IEnumerable<Type> MethodParameterTypes { get; internal set; }

		public Boolean TakesTypedReference()
		{
			var instr = this;

			if (instr.MethodReturnType == typeof(TypedReference)) return true;

			return instr.MethodParameterTypes.Any(p => p == typeof(TypedReference));
		}

		public Boolean TakesManagedPointer()
		{
			var instr = this;

			if (instr.MethodReturnType.IsPointer) return true;

			return instr.MethodParameterTypes.Any(p => p.IsPointer);
		}

		internal Boolean TakesByRefArgs()
		{
			var instr = this;

			return instr.MethodParameterTypes.Any(p => p.IsByRef);
		}
	}

	internal class BufferedILGenerator<DelegateType>
	{
		public BufferedILInstruction this[Int32 ix]
		{
			get
			{
				return TraversableBuffer[ix];
			}
		}

		public Int32 Index { get { return Buffer.Count; } }

		private List<Action<ILGenerator, Boolean, StringBuilder>> Buffer = new List<Action<ILGenerator, Boolean, StringBuilder>>();
		private List<BufferedILInstruction> TraversableBuffer = new List<BufferedILInstruction>();
		internal List<Operation<DelegateType>> Operations = new List<Operation<DelegateType>>();
		private List<Func<Int32>> InstructionSizes = new List<Func<Int32>>();

		public BufferedILGenerator()
		{
		}

		public String UnBuffer(ILGenerator il)
		{
			var log = new StringBuilder();

			// First thing will always be a Mark for tracing purposes; no reason to actually do it
			for (var i = 2; i < Buffer.Count; i++)
			{
				var x = Buffer[i];

				x(il, false, log);
			}

			return log.ToString();
		}

		private Dictionary<Int32, Int32> LengthCache = new Dictionary<Int32, Int32>();

		private Int32 LengthTo(Int32 end)
		{
			if (end == 0)
			{
				return 0;
			}

			Int32 cached;
			if (LengthCache.TryGetValue(end, out cached))
			{
				return cached;
			}

			Int32 runningTotal = 0;

			for (var i = 0; i < end; i++)
			{
				var s = InstructionSizes[i];

				runningTotal += s();

				LengthCache[i + 1] = runningTotal;
			}

			cached = LengthCache[end];

			return cached;
		}

		internal String[] Instructions(List<Local> locals)
		{
			var ret = new List<String>();

			var invoke = typeof(DelegateType).GetMethod("Invoke");
			var returnType = invoke.ReturnType;
			var parameterTypes = invoke.GetParameters().Select(s => s.ParameterType).ToArray();

			var dynMethod = new DynamicMethod(Guid.NewGuid().ToString(), returnType, parameterTypes);
			var il = dynMethod.GetILGenerator();

			var instrs = new StringBuilder();

			for (var i = 0; i < Buffer.Count; i++)
			{
				var x = Buffer[i];

				x(il, true, instrs);
				var line = instrs.ToString().TrimEnd();

				if (line.StartsWith(OpCodes.Ldloc_0.ToString()) ||
						line.StartsWith(OpCodes.Stloc_0.ToString()))
				{
					line += " // " + GetInScopeAt(locals, i)[0];
				}

				if (line.StartsWith(OpCodes.Ldloc_1.ToString()) ||
						line.StartsWith(OpCodes.Stloc_1.ToString()))
				{
					line += " // " + GetInScopeAt(locals, i)[1];
				}

				if (line.StartsWith(OpCodes.Ldloc_2.ToString()) ||
						line.StartsWith(OpCodes.Stloc_2.ToString()))
				{
					line += " // " + GetInScopeAt(locals, i)[2];
				}

				if (line.StartsWith(OpCodes.Ldloc_3.ToString()) ||
						line.StartsWith(OpCodes.Stloc_3.ToString()))
				{
					line += " // " + GetInScopeAt(locals, i)[3];
				}

				if (line.StartsWith(OpCodes.Ldloc_S.ToString()) ||
						line.StartsWith(OpCodes.Stloc_S.ToString()))
				{
					line += " // " + ExtractLocal(line, locals, i);
				}

				ret.Add(line);
				instrs.Length = 0;
			}

			return ret.ToArray();
		}

		private static Dictionary<Int32, Local> GetInScopeAt(List<Local> allLocals, Int32 ix)
		{
			return
					allLocals
							.Where(
									l =>
											l.DeclaredAtIndex <= ix &&
											(l.ReleasedAtIndex == null || l.ReleasedAtIndex > ix)
							).ToDictionary(d => (Int32)d.Index, d => d);
		}

		private static Regex _ExtractLocal = new Regex(@"\s+(?<locId>\d+)", RegexOptions.Compiled);

		private static Local ExtractLocal(String from, List<Local> locals, Int32 ix)
		{
			var match = _ExtractLocal.Match(from);

			var locId = match.Groups["locId"].Value;

			var lid = Int32.Parse(locId);

			return GetInScopeAt(locals, ix)[lid];
		}

		public Int32 ByteDistance(Int32 start, Int32 stop)
		{
			var toStart = LengthTo(start);
			var toStop = LengthTo(stop);

			return toStop - toStart;
		}

		public void Remove(Int32 ix)
		{
			if (ix < 0 || ix >= Buffer.Count)
			{
				throw new ArgumentOutOfRangeException("ix", "Expected value between 0 and " + Buffer.Count);
			}

			LengthCache.Clear();

			InstructionSizes.RemoveAt(ix);

			Buffer.RemoveAt(ix);

			TraversableBuffer.RemoveAt(ix);

			Operations.RemoveAt(ix);
		}

		public void Insert(Int32 ix, OpCode op)
		{
			if (ix < 0 || ix > Buffer.Count)
			{
				throw new ArgumentOutOfRangeException("ix", "Expected value between 0 and " + Buffer.Count);
			}

			LengthCache.Clear();

			InstructionSizes.Insert(ix, () => InstructionSize.Get(op));

			Buffer.Insert(
					ix,
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op);
						}

						if (ExtensionMethods.IsPrefix(op))
						{
							log.Append(op.ToString());
						}
						else
						{
							log.AppendLine(op.ToString());
						}
					}
			);

			TraversableBuffer.Insert(
					ix,
					new BufferedILInstruction
					{
						IsInstruction = op
					}
			);

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[0] });
		}

		public void Emit(OpCode op)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op);
						}

						if (ExtensionMethods.IsPrefix(op))
						{
							log.Append(op.ToString());
						}
						else
						{
							log.AppendLine(op.ToString());
						}
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[0] });
		}

		public void Emit(OpCode op, Byte b)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, b);
						}

						if (ExtensionMethods.IsPrefix(op))
						{
							log.Append(op + "" + b + ".");
						}
						else
						{
							log.AppendLine(op + " " + b);
						}
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { b } });
		}

		public void Emit(OpCode op, Int16 s)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, s);
						}

						if (ExtensionMethods.IsPrefix(op))
						{
							log.Append(op + "" + s + ".");
						}
						else
						{
							log.AppendLine(op + " " + s);
						}
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { s } });
		}

		public void Emit(OpCode op, Int32 i)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, i);
						}

						if (ExtensionMethods.IsPrefix(op))
						{
							log.Append(op + "" + i + ".");
						}
						else
						{
							log.AppendLine(op + " " + i);
						}
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { i } });
		}

		public void Emit(OpCode op, UInt32 ui)
		{
			Int32 asInt;
			unchecked
			{
				asInt = (Int32)ui;
			}

			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, asInt);
						}

						log.AppendLine(op + " " + ui);
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { ui } });
		}

		public void Emit(OpCode op, Int64 l)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, l);
						}

						log.AppendLine(op + " " + l);
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { l } });
		}

		public void Emit(OpCode op, UInt64 ul)
		{
			Int64 asLong;
			unchecked
			{
				asLong = (Int64)ul;
			}

			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, asLong);
						}

						log.AppendLine(op + " " + ul);
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { ul } });
		}

		public void Emit(OpCode op, Single f)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, f);
						}

						log.AppendLine(op + " " + f);
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { f } });
		}

		public void Emit(OpCode op, Double d)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, d);
						}

						log.AppendLine(op + " " + d);
					});

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { d } });
		}

		public void Emit(OpCode op, MethodInfo method, IEnumerable<Type> parameterTypes)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, method);
						}

						var mtdString = method is MethodBuilder ? method.Name : method.ToString();

						log.AppendLine(op + " " + mtdString);
					}
			);

			var parameters = new List<Type>(parameterTypes);
			if (!method.IsStatic)
			{
				var declaring = method.DeclaringType;

				if (declaring.IsValueType)
				{
					declaring = declaring.MakePointerType();
				}

				parameters.Insert(0, declaring);
			}

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op, MethodReturnType = method.ReturnType, MethodParameterTypes = parameters });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { method } });
		}

		public void Emit(OpCode op, ConstructorInfo cons, IEnumerable<Type> parameterTypes)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, cons);
						}

						var mtdString = cons is ConstructorBuilder ? cons.Name : cons.ToString();

						log.AppendLine(op + " " + mtdString);
					}
			);

			var parameters = new List<Type>(parameterTypes);
			var declaring = cons.DeclaringType;

			if (declaring.IsValueType)
			{
				declaring = declaring.MakePointerType();
			}

			parameters.Insert(0, declaring);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op, MethodReturnType = typeof(void), MethodParameterTypes = parameters });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { cons } });
		}

		public void Emit(OpCode op, ConstructorInfo cons)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, cons);
						}

						log.AppendLine(op + " " + cons);
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { cons } });
		}

		public void Emit(OpCode op, Type type)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, type);
						}

						log.AppendLine(op + " " + type);
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { type } });
		}

		public void Emit(OpCode op, FieldInfo field)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, field);
						}

						log.AppendLine(op + " " + field);
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { field } });
		}

		public void Emit(OpCode op, String str)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.Emit(op, str);
						}

						log.AppendLine(op + " '" + str.Replace("'", @"\'") + "'");
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { str } });
		}

		public void Emit(OpCode op, Sigil.Label label, out UpdateOpCodeDelegate update)
		{
			var localOp = op;

			update =
					newOpcode =>
					{
						LengthCache.Clear();

						localOp = newOpcode;
					};

			InstructionSizes.Add(() => InstructionSize.Get(localOp));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							var l = label.LabelDel(il);
							il.Emit(localOp, l);
						}

						log.AppendLine(localOp + " " + label);
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { label } });
		}

		public void Emit(OpCode op, Sigil.Label[] labels, out UpdateOpCodeDelegate update)
		{
			var localOp = op;

			update =
					newOpcode =>
					{
						LengthCache.Clear();

						localOp = newOpcode;
					};

			InstructionSizes.Add(() => InstructionSize.Get(localOp, labels));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							var ls = labels.Select(l => l.LabelDel(il)).ToArray();
							il.Emit(localOp, ls);
						}

						log.AppendLine(localOp + " " + Join(", ", labels));
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = labels });
		}

		internal static String Join<T>(String delimiter, IEnumerable<T> parts) where T : class
		{
			using (var iter = parts.GetEnumerator())
			{
				if (!iter.MoveNext()) return "";
				var sb = new StringBuilder();
				var next = iter.Current;
				if (next != null) sb.Append(next);
				while (iter.MoveNext())
				{
					sb.Append(delimiter);
					next = iter.Current;
					if (next != null) sb.Append(next);
				}
				return sb.ToString();
			}
		}

		public void Emit(OpCode op, Sigil.Local local)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							var l = local.LocalDel(il);
							il.Emit(op, l);
						}

						log.AppendLine(op + " " + local);
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = new Object[] { local } });
		}

		public void Emit(OpCode op, CallingConventions callConventions, Type returnType, Type[] parameterTypes)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.EmitCalli(op, callConventions, returnType, parameterTypes, null);
						}

						log.AppendLine(op + " " + callConventions + " " + returnType + " " + Join(" ", parameterTypes));
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op, MethodReturnType = returnType, MethodParameterTypes = parameterTypes });

			var paras = new List<Object> { callConventions, returnType };
			paras.AddRange(parameterTypes);

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = paras.ToArray() });
		}

		public void EmitCall(OpCode op, MethodInfo method, IEnumerable<Type> parameterTypes, Type[] arglist)
		{
			InstructionSizes.Add(() => InstructionSize.Get(op));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.EmitCall(op, method, arglist);
						}

						var mtdString = method is MethodBuilder ? method.Name : method.ToString();

						log.AppendLine(op + " " + mtdString + " __arglist(" + Join(", ", arglist) + ")");
					}
			);

			var parameters = new List<Type>(parameterTypes);
			if (!method.IsStatic)
			{
				var declaring = method.DeclaringType;

				if (declaring.IsValueType)
				{
					declaring = declaring.MakePointerType();
				}

				parameters.Insert(0, declaring);
			}

			parameters.AddRange(arglist);

			var paras = new List<Object> { method };
			paras.AddRange(arglist);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = op, MethodReturnType = method.ReturnType, MethodParameterTypes = parameters });

			Operations.Add(new Operation<DelegateType> { OpCode = op, Parameters = paras.ToArray() });
		}

		public void EmitCalli(CallingConventions callingConvention, Type returnType, Type[] parameterTypes, Type[] arglist)
		{
			InstructionSizes.Add(() => InstructionSize.Get(OpCodes.Calli));

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.EmitCalli(OpCodes.Calli, callingConvention, returnType, parameterTypes, arglist);
						}

						log.AppendLine(OpCodes.Calli + " " + callingConvention + " " + returnType + " " + Join(" ", (IEnumerable<Type>)parameterTypes) + " __arglist(" + Join(", ", arglist) + ")");
					}
			);

			var ps = new List<Type>(parameterTypes);
			ps.AddRange(arglist);

			TraversableBuffer.Add(new BufferedILInstruction { IsInstruction = OpCodes.Calli, MethodReturnType = returnType, MethodParameterTypes = ps });

			var paras = new List<Object>() { callingConvention, returnType };
			paras.AddRange(parameterTypes);
			paras.AddRange(arglist);

			Operations.Add(new Operation<DelegateType> { OpCode = OpCodes.Calli, Parameters = paras.ToArray() });
		}

		public DefineLabelDelegate BeginExceptionBlock()
		{
			ILGenerator forIl = null;
			System.Reflection.Emit.Label? l = null;

			DefineLabelDelegate ret =
					il =>
					{
						if (forIl != null && forIl != il)
						{
							l = null;
						}

						if (l != null) return l.Value;

						forIl = il;
						l = forIl.BeginExceptionBlock();

						return l.Value;
					};

			InstructionSizes.Add(() => InstructionSize.BeginExceptionBlock());

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							ret(il);
						}

						log.AppendLine("--BeginExceptionBlock--");
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { StartsExceptionBlock = true });

			Operations.Add(null);

			return ret;
		}

		public void BeginCatchBlock(Type exception)
		{
			InstructionSizes.Add(() => InstructionSize.BeginCatchBlock());

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.BeginCatchBlock(exception);
						}

						log.AppendLine("--BeginCatchBlock(" + exception + ")--");
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { StartsCatchBlock = true });

			Operations.Add(null);
		}

		public void EndExceptionBlock()
		{
			InstructionSizes.Add(() => InstructionSize.EndExceptionBlock());

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.EndExceptionBlock();
						}

						log.AppendLine("--EndExceptionBlock--");
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { EndsExceptionBlock = true });

			Operations.Add(null);
		}

		public void EndCatchBlock()
		{
			InstructionSizes.Add(() => InstructionSize.EndCatchBlock());

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						log.AppendLine("--EndCatchBlock--");
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { EndsCatchBlock = true });

			Operations.Add(null);
		}

		public void BeginFinallyBlock()
		{
			InstructionSizes.Add(() => InstructionSize.BeginFinallyBlock());

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							il.BeginFinallyBlock();
						}

						log.AppendLine("--BeginFinallyBlock--");
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { StartsFinallyBlock = true });

			Operations.Add(null);
		}

		public void EndFinallyBlock()
		{
			InstructionSizes.Add(() => InstructionSize.EndFinallyBlock());

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						log.AppendLine("--EndFinallyBlock--");
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { EndsFinallyBlock = true });

			Operations.Add(null);
		}

		public DefineLabelDelegate DefineLabel()
		{
			ILGenerator forIl = null;
			System.Reflection.Emit.Label? l = null;

			DefineLabelDelegate ret =
					il =>
					{
						if (forIl != null && forIl != il)
						{
							l = null;
						}

						if (l != null) return l.Value;

						forIl = il;
						l = forIl.DefineLabel();

						return l.Value;
					};

			InstructionSizes.Add(() => InstructionSize.DefineLabel());

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							ret(il);
						}
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { DefinesLabel = true });

			Operations.Add(null);

			return ret;
		}

		public void MarkLabel(Sigil.Label label)
		{
			InstructionSizes.Add(() => InstructionSize.MarkLabel());

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							var l = label.LabelDel(il);
							il.MarkLabel(l);
						}

						log.AppendLine();
						log.AppendLine(label + ":");
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { MarksLabel = label });

			Operations.Add(null);
		}

		public DeclareLocallDelegate DeclareLocal(Type type)
		{
			ILGenerator forIl = null;
			LocalBuilder l = null;

			DeclareLocallDelegate ret =
					il =>
					{
						if (forIl != null && il != forIl)
						{
							l = null;
						}

						if (l != null) return l;

						forIl = il;
						l = forIl.DeclareLocal(type);

						return l;
					};

			InstructionSizes.Add(() => InstructionSize.DeclareLocal());

			LengthCache.Clear();

			Buffer.Add(
					(il, logOnly, log) =>
					{
						if (!logOnly)
						{
							ret(il);
						}
					}
			);

			TraversableBuffer.Add(new BufferedILInstruction { DeclaresLocal = true });

			Operations.Add(null);

			return ret;
		}
	}
}